export const enum ColorMode {
    hex=1,
    rgb=2,
    rgba=3,
    hsl=4,
    hsla=5
}

export const ColorReg:{[k:string]:RegExp} = {
    hex:/^#[0-9abcdefABCDEF]{3,8}$/,
    rgb:/^rgb\((.*),(.*),(.*)\)$/,
    rgba:/^rgba\((.*),(.*),(.*),(.*)\)$/,
    hsl:/^hsl\((.*),(.*%),(.*%)\)$/,
    hsla:/^hsla\((.*),(.*%),(.*%),(.*)\)$/,
    percent:/^[\d]*\.?[\d]+%$/,
    number:/^[\d]*\.?[\d]+$/
}
export function createRgba(r:number,g:number,b:number,a?:number):string{
    a=a===undefined?1:a
    return `rgba(${Math.round(r)},${Math.round(g)},${Math.round(b)},${a})`
}
export function createHsla(h:number,s:number,l:number,a?:number):string{
    a=a===undefined?1:a
    return `hsla(${h}deg,${Math.round(s*100)}%,${Math.round(l*100)}%,${a})`
}
export function rgb2hex(r:number,g:number,b:number,a?:number):string{
    let hex = "#"+n2hex(r)+n2hex(g)+n2hex(b)
    if(a!==undefined&&a<1)
    hex+=n2hex(a*255)
    return hex
}
export function hsb2hex(h:number,s:number,v:number,a:number):string{
    const [r,g,b] = hsb2rgb(h,s,v)
    return rgb2hex(r,g,b,a)
}
export function parseColor(color:string) :{
    mode:ColorMode,
    rgba:number[]|boolean
}{
    if(!color)return {
        mode:ColorMode.hex,
        rgba:false
    }
    let 
        rgba:number[]|boolean,
        mode:ColorMode;
    const c = color.replace(/\s+/g,"")
    const prefix = (c.match(/^\w+/)||c.match(/^#/)||[])[0];
    switch(prefix){
        case "#":
            rgba = hex2rgb(c);
            mode = ColorMode.hex
            break
        case "rgb":
            rgba = rgb2Rgb(c);
            mode = ColorMode.rgba
            break
        case "rgba":
            rgba = rgba2Rgb(c);
            mode = ColorMode.rgba
            break
        case "hsl":
            rgba = hsl2Rgb(c);
            mode = ColorMode.hsla
            break
        case "hsla":
            rgba = hsla2Rgb(c);
            mode = ColorMode.hsla
            break
        default:
            mode = ColorMode.hex
            rgba = false
    }
    return {mode,rgba:rgba}
}
export function hex2rgb(color:string):boolean|number[]{
    const 
        c = color.substr(1),
        l = c.length;
    let res:number[] = [],a:number;
    if(![3,6,8].includes(c.length)) return false
    if(!/^[0-9abcdefABCDEF]+$/.test(c)) return false
    
    switch(l){
        case 3:
            res = c.split("").map<number>((h:string)=>hex2n(h)*255/15)
            break
        case 6:
        case 8:
            res = (c.match(/\w{2}/g) as Array<string>).map((h:string)=>hex2n(h))
    }

    a = res[3];
    if(a){
        a= Math.max(res[3],0)
        if(a>1)a = +Number(a/255).toFixed(3)
        res[3] = a;
    }
    return res
}
export function rgb2Rgb(color:string):number[]|boolean{
    const raw = color.match(ColorReg.rgb)
    if(!raw)return false
    const rgb = raw.slice(1).map((s:string)=>parseInt(s))
    if(rgb.some((n:number)=>n<0||n>255))return false
    return rgb as number[]
}
export function parseAlpha(a:string):number{
    let na:number; 
    if(a.endsWith("%")){
        a = a.substr(0,a.length-1)
        na = Number(a)/100
    }else if(Number(a)>1){
        na = Number(a)/255
    }else na = Number(a)
    na = na||0
    na = parseFloat(na.toFixed(2))
    na = Math.max(na,0)
    na = Math.min(na,1)
    return na
}
export function rgba2Rgb(color:string):number[]|boolean{
    const raw = color.match(ColorReg.rgba)
    if(!raw)return false
    const rgb = raw.slice(1,4).map((s:string)=>parseFloat(s))
    const a = parseAlpha(raw[4])
    if(rgb.some((n:number)=>n<0||n>255))return false
    return [...rgb,a]
}
export function hsl2Rgb(color:string):boolean|number[]{
    const raw = color.match(ColorReg.hsl)
    if(!raw)return false
    if(!raw[2].endsWith("%"))return false
    if(!raw[3].endsWith("%"))return false

    const hsl = raw.slice(1,4)
    const h = parseInt(hsl[0])
    const s = parseInt(hsl[1])/100
    const l = parseInt(hsl[2])/100
    if(h<0||h>360||s<0||s>1||l<0||l>1)return false
    return hsl2rgb(h,s,l)
}
export function hsla2Rgb(color:string):boolean|number[]{
    const raw = color.match(ColorReg.hsla)
    if(!raw)return false
    if(!raw[2].endsWith("%"))return false
    if(!raw[3].endsWith("%"))return false

    const hsl = raw.slice(1,4)
    const h = parseInt(hsl[0])
    const s = parseInt(hsl[1])/100
    const l = parseInt(hsl[2])/100
    const a = parseAlpha(raw[4])
    if(h<0||h>360||s<0||s>1||l<0||l>1)return false
    const rgb = hsl2rgb(h,s,l)
    return [...rgb,a]
}

export function hex2n(h:string):number{
    return parseInt(h,16)
}
export function  n2hex(n:number):string{
    n = n||0
    n = Math.max(n,0)
    n = Math.min(255,n)
    const hex = Math.round(n).toString(16)
    return hex.length==1?"0"+hex:hex
}
export function rgb2hsl(r:number,g:number,b:number):[number,number,number]{
	const 
		r1 = r/255,
		g1 = g/255,
		b1 = b/255,
		cmax = Math.max(r1,g1,b1),
		cmin = Math.min(r1,g1,b1),
		x = cmax - cmin;
    let h1 = 0 , s1 ;
    const l1 = (cmax + cmin)/2;
    {
        if (cmax === cmin) {
            h1 =  s1 = 0
        } else {
            s1 = l1 > 0.5 ? x / (2 - cmax - cmin) : x / (cmax + cmin);
            switch (cmax) {
                case r1:
                    h1 = (g1-b1)/x+(g1<b1?6:0)
                    break
                case g1:
                    h1 = (b1-r1)/x+2
                    break
                case b1:
                    h1 = (r1-g1)/x+4
                    break
            }
            h1/=6 
        }
	}
	const 
		h = Math.round(h1*360),
		s = s1,
		v = l1;
	return [h,s,v] as [number,number,number]
}

/*
When 0 ≤ H < 360, 0 ≤ S ≤ 1 and 0 ≤ V ≤ 1:
*/
export function hsl2rgb(h1:number,s1:number,v1:number) :number[]{
    const h = Math.round(h1) / 360,
        s = s1,
        l = v1;
    let r:number, g:number, b:number;
        if (s === 0) {
            r = g = b = l;
        } else {
            const hue2rgb = function hue2rgb(p:number, q:number, t:number) {
                if (t < 0)
                    t += 1;
                if (t > 1)
                    t -= 1;
                if (t < 1 / 6)
                    return p + (q - p) * 6 * t;
                if (t < 1 / 2)
                    return q;
                if (t < 2 / 3)
                    return p + (q - p) * (2 / 3 - t) * 6;
                return p;
            };
            const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
            const p = 2 * l - q;
            r = hue2rgb(p, q, h + 1 / 3);
            g = hue2rgb(p, q, h);
            b = hue2rgb(p, q, h - 1 / 3);
        }
        return [r*255,g*255,b*255].map(n=>Math.round(n))
}
export function hsla2hex(h:number,s:number,v:number,a?:number):string{
    const hsv = hsl2rgb(h,s,v);
    return rgb2hex(hsv[0],hsv[1],hsv[2],a)
}
export function rgb2hsb(R:number,G:number,B:number):number[]{
    const 
        r = R/255,
        g = G/255,
        b = B/255,
        max = Math.max(r,g,b),
        min = Math.min(r,g,b),
        x = max-min;
        let h =0;
        if(x==0)
            h = 0
        else 
            switch(max){
                case r:
                    // h = 60*(g-b)/x+(g>b?0:360);
                    h = 60*((g-b)/x%6)
                    h = h>0?h:h+360
                    break;
                case g:
                    h = 60*((b-r)/x+2)
                    break
                case b:
                    h = 60*((r-g)/x+4)

            }
        const 
            s:number = max?x/max:0,
            v:number = max;
    return [h,s,v]
}

export function hsb2rgb(h:number,s:number,v:number):number[]{
    const 
        c = v*s,
        x = c*(1 - Math.abs(h/60%2 - 1)),
        m = v-c;
    const matrix = [
        [c,x,0],
        [x,c,0],
        [0,c,x],
        [0,x,c],
        [x,0,c],
        [c,0,x]
    ]
    return matrix[Math.floor(h/60)%6].map((n:number)=>Math.round((n+m)*255))
}